//
// Created by edward on 23-3-16.
//

#include "global.h"
#include "Model.h"
#include "Shader.h"


Model::Model(const std::string &path)
    : showBorder_(false)
    , borderShader(std::make_unique<Shader>("../shader/vertex.glsl", "../shader/borderFragment.glsl"))
    {
    Assimp::Importer importer;

    /// aiProcess_Triangulate:  if the model does not (entirely) consist of triangles, it should transform all the model's primitive shapes to triangles first
    /// aiProcess_FlipUVs: flips the texture coordinates on the y-axis where necessary during processing
    const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | aiProcess_FlipUVs);
    if(!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) {
        std::cout << "ERROR::ASSIMP::" << importer.GetErrorString() << std::endl;
        return;
    }
    directory_ = path.substr(0, path.find_last_of('/') + 1);
    traverse(scene->mRootNode, scene);

    borderShader->use();
    auto color = glm::vec4(0.04, 0.28, 0.26, 1.0);
    borderShader->set("borderColor", color);
}

void Model::traverse(aiNode *node, const aiScene *scene) {
    // process all the node's meshes (if any)
    for(unsigned int i = 0; i < node->mNumMeshes; i++) {
        aiMesh *mesh = scene->mMeshes[node->mMeshes[i]];
        meshes_.push_back(createMesh(mesh, scene));
    }
    // then do the same for each of its children
    for(unsigned int i = 0; i < node->mNumChildren; i++) {
        traverse(node->mChildren[i], scene);
    }
}

Mesh Model::createMesh(aiMesh *mesh, const aiScene *scene) {
    std::vector<Vertex> vertices;
    std::vector<unsigned int> indices;
    std::vector<Texture> textures;

    for(unsigned int i = 0; i < mesh->mNumVertices; i++) {
        Vertex vertex;
        // process vertex positions, normals and texture coordinates
        glm::vec3 vec;
        auto &mVertex = mesh->mVertices[i];
        vec.x = mVertex.x;
        vec.y = mVertex.y;
        vec.z = mVertex.z;
        vertex.position = vec;



        auto &mNormal = mesh->mNormals[i];
        vec.x = mNormal.x;
        vec.y = mNormal.y;
        vec.z = mNormal.z;
        vertex.normal = vec;

        if(mesh->mTextureCoords[0]) { // does the mesh contain texture coordinates?
            glm::vec2 vec;
            vec.x = mesh->mTextureCoords[0][i].x;
            vec.y = mesh->mTextureCoords[0][i].y;
            vertex.texCoords = vec;
        } else vertex.texCoords = glm::vec2(0.0f, 0.0f);

        vertices.push_back(vertex);
    }

    for(unsigned int i = 0; i < mesh->mNumFaces; i++) {
        aiFace face = mesh->mFaces[i];
        for(unsigned int j = 0; j < face.mNumIndices; j++)
            indices.push_back(face.mIndices[j]);
    }

    if(mesh->mMaterialIndex >= 0) {
        aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex];
        std::vector<Texture> diffuseMaps = loadMaterialTextures(material,
                                                           aiTextureType_DIFFUSE, "textureDiffuse");
        textures.insert(textures.end(), diffuseMaps.begin(), diffuseMaps.end());
        std::vector<Texture> specularMaps = loadMaterialTextures(material,
                                                            aiTextureType_SPECULAR, "textureSpecular");
        textures.insert(textures.end(), specularMaps.begin(), specularMaps.end());

        // Get the diffuse color of the material
        aiColor4D diffuseColor(0.0f, 0.0f, 0.0f, 1.0f);
        material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor);

        // Convert the color from aiColor4D to glm::vec3
        glm::vec3 diffuse(diffuseColor.r, diffuseColor.g, diffuseColor.b);
        // Add the diffuse color to each vertex
        for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
            vertices[i].color = diffuse;
        }
    }


    return Mesh(vertices, indices, textures);
}

std::vector<Texture> Model::loadMaterialTextures(aiMaterial *mat, aiTextureType type, std::string typeName) {
    std::vector<Texture> textures;
    for(unsigned int i = 0; i < mat->GetTextureCount(type); i++) {
        aiString str;
        mat->GetTexture(type, i, &str);
        textures.emplace_back(directory_ + str.C_Str(), typeName);
    }
    return textures;
}

void Model::drawBorder(Shader *shader) {

    borderShader->use();

    glm::mat4 mtx;
    shader->get("model", mtx);
    mtx = glm::scale(mtx, glm::vec3(1.1f));
    borderShader->setModelMatrix(mtx);
    shader->get("view", mtx);
    borderShader->setViewMatrix(mtx);
    shader->get("projection", mtx);
    borderShader->setProjectionMatrix(mtx);

    glStencilFunc(GL_NOTEQUAL, 1, 0xFF);
    glStencilMask(0x00);    ///不设置模板测试，使得边框可以被覆盖

    for (auto &mesh : meshes_) {
        mesh.draw(borderShader.get());
    }

}

void Model::draw(Shader *shader) {
    shader->use();

    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glStencilFunc(GL_ALWAYS, 1, 0xFF);
    glStencilMask(0xFF);

//    shader->set("material.emit", 2);
    for (auto &mesh : meshes_) {
        mesh.draw(shader);
    }

    if (showBorder_) {
        drawBorder(shader);
    }

    glStencilMask(0xFF);
    glStencilFunc(GL_ALWAYS, 1, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

}

Model::~Model() {
}

void Model::showBorder() {
    showBorder_ = true;
}

void Model::hideBorder() {
    showBorder_ = false;
}
